package com.yxsk.relay.job.admin.data.service;

import com.yxsk.relay.job.admin.core.schedule.constant.JobDataConstant;
import com.yxsk.relay.job.admin.core.schedule.quartz.QuartzDynamicScheduleService;
import com.yxsk.relay.job.admin.core.schedule.quartz.vo.JobConfig;
import com.yxsk.relay.job.admin.data.aop.DependOnScheduleServiceFilter;
import com.yxsk.relay.job.admin.data.entity.BaseEntity;
import com.yxsk.relay.job.admin.data.entity.JobApps;
import com.yxsk.relay.job.admin.data.entity.JobDetails;
import com.yxsk.relay.job.admin.data.entity.vo.QuartzJobInfo;
import com.yxsk.relay.job.admin.data.repository.JobDetailsRepository;
import com.yxsk.relay.job.admin.utils.IdUtils;
import com.yxsk.relay.job.component.admin.utils.SpringBeanUtils;
import com.yxsk.relay.job.component.common.exception.RelayJobRuntimeException;
import org.quartz.Job;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.text.MessageFormat;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @Author 11376
 * @CreaTime 2019/6/5 18:25
 * @Description
 */
@Service
@Transactional
public class JobDetailService extends AbstractService<JobDetails, JobDetailsRepository> {

    @Autowired(required = false)
    private QuartzDynamicScheduleService scheduleConfigService;
    @Autowired
    private JobAppsService appsService;

    @Transactional(propagation = Propagation.SUPPORTS)
    public QuartzJobInfo getJobConfig(String jobId) {
        // 获取 jobDetails
        Optional<JobDetails> optional = this.repository.findById(jobId);
        if (!optional.isPresent()) {
            throw new RelayJobRuntimeException(MessageFormat.format("Not found job config of id[{0}]", jobId));
        }
        JobDetails jobDetails = optional.get();
        return convert(jobDetails);
    }

    public void addJob(JobDetails jobDetail) {
        // 防止事务未提交而任务已触发
        SpringBeanUtils.getBean(JobDetailService.class).saveAndCommitJob(jobDetail);
        if (StringUtils.hasLength(jobDetail.getCronExpression())) {
            // 有可能不是定时任务
            this.scheduleConfigService.registerJob(this.convert(jobDetail));
            // TODO 集群通知
        }
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void saveAndCommitJob(JobDetails jobDetail) {
        jobDetail.setId(IdUtils.nextId());
        this.repository.save(jobDetail);
    }

    public void pauseJob(String jobId) {
        Optional<JobDetails> optional = this.repository.findById(jobId);
        if (!optional.isPresent()) {
            throw new RelayJobRuntimeException(MessageFormat.format("Not found job config of id[{0}]", jobId));
        }
        JobDetails jobDetails = optional.get();
        if (BaseEntity.DelFlagEnum.DELETED.getFlag().equals(jobDetails.getDelFlag())) {
            throw new RelayJobRuntimeException(MessageFormat.format("The job has been deleted, job id[{0}]", jobId));
        }
        jobDetails.setStatus(JobConfig.JobStatus.PAUSE.getStatus());
        this.repository.save(jobDetails);
        this.scheduleConfigService.pauseJob(this.convert(jobDetails));
        // TODO 集群通知
    }

    public void pauseAllJob() {
        List<JobDetails> jobDetails = this.repository.findByDelFlag(BaseEntity.DelFlagEnum.NORMAL.getFlag());
        for (JobDetails jobDetail : jobDetails) {
            if (JobConfig.JobStatus.NORMAL.getStatus().equals(jobDetail.getStatus())) {
                jobDetail.setStatus(JobConfig.JobStatus.PAUSE.getStatus());
            }
        }
        this.repository.saveAll(jobDetails);
        this.scheduleConfigService.pauseAllJob();
        // TODO 集群通知
    }

    public void resumeJob(String jobId) {
        Optional<JobDetails> optional = this.repository.findById(jobId);
        if (!optional.isPresent()) {
            throw new RelayJobRuntimeException(MessageFormat.format("Not found job config of id[{0}]", jobId));
        }
        JobDetails jobDetails = optional.get();
        if (JobConfig.JobStatus.ABANDONED.getStatus().equals(jobDetails.getStatus())) {
            throw new RelayJobRuntimeException(MessageFormat.format("The job has been abandoned, job id[{0}]", jobId));
        }
        if (JobConfig.JobStatus.PAUSE.getStatus().equals(jobDetails.getStatus())) {
            jobDetails.setStatus(JobConfig.JobStatus.NORMAL.getStatus());
            this.repository.save(jobDetails);
            this.scheduleConfigService.resumeJob(this.convert(jobDetails));
            // TODO 集群通知
        }
    }

    public void resumeAllJob() {
        List<JobDetails> jobDetails = this.repository.findByDelFlag(BaseEntity.DelFlagEnum.NORMAL.getFlag());
        for (JobDetails details : jobDetails) {
            if (JobConfig.JobStatus.PAUSE.getStatus().equals(details.getStatus())) {
                details.setStatus(JobConfig.JobStatus.NORMAL.getStatus());
            }
        }
        this.repository.saveAll(jobDetails);
        this.scheduleConfigService.resumeAllJob();
        // TODO 集群通知
    }

    @Transactional(propagation = Propagation.SUPPORTS)
    public List<JobDetails> findByAppId(String appId) {
        return this.repository.findByAppIdAndDelFlag(appId, BaseEntity.DelFlagEnum.NORMAL.getFlag());
    }

    @DependOnScheduleServiceFilter.NotCheckScheduleService
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public QuartzJobInfo convert(JobDetails details) {
        Assert.notNull(details, "Job detail not be null");
        QuartzJobInfo jobConfig = new QuartzJobInfo();
        jobConfig.setId(details.getId());
        jobConfig.setHandlerName(details.getHandlerName());
        // 获取应用名称
        JobApps apps = this.appsService.findById(details.getAppId());
        if (apps == null || BaseEntity.DelFlagEnum.DELETED.getFlag().equals(apps.getDelFlag())) {
            throw new RelayJobRuntimeException("未找到任务相关应用信息");
        }
        jobConfig.setAppName(apps.getAppName());
        jobConfig.setCron(details.getCronExpression());
        jobConfig.setExecuteParam(details.getTriggerParam());
        jobConfig.setExecuteTimeout(Long.valueOf(details.getExecuteTimeout() == null ? 0 : details.getExecuteTimeout()));
        try {
            jobConfig.setScheduleTaskClass((Class<? extends Job>) Class.forName(details.getTriggerClassName()));
        } catch (ClassNotFoundException e) {
            throw new RelayJobRuntimeException(e);
        }
        jobConfig.setExecuteModel(JobConfig.ExecuteModel.getExecuteModel(details.getExecuteModel()));
        jobConfig.setRouteStrategy(JobConfig.RouteStrategy.getStrategy(details.getRouteStrategy()));
        jobConfig.setBlockStrategy(JobConfig.BlockStrategy.getBlockStrategy(details.getBlockStrategy()));
        jobConfig.setStatus(JobConfig.JobStatus.getJobStatus(details.getStatus()));
        jobConfig.setFailRetryTimes(details.getFailRetryTimes());
        return jobConfig;
    }

    public void addChildren(String jobId, String childrenId) {
        Assert.hasLength(childrenId, "子任务Id不能为空");
        Assert.hasLength(jobId, "任务编号不能为空");
        String[] childIds = childrenId.split(",");
        if (childIds == null) {
            throw new RelayJobRuntimeException("添加的子任务为空");
        }
        Optional<JobDetails> optional = this.repository.findById(jobId);
        if (!optional.isPresent() || BaseEntity.DelFlagEnum.DELETED.getFlag().equals(optional.get().getDelFlag())) {
            throw new RelayJobRuntimeException("未找到相关任务");
        }
        // 查询已存在的子任务
        List<String> detailsList = this.repository.findChildrenIds(jobId);
        List<String> addIds;
        if (!CollectionUtils.isEmpty(detailsList)) {
            addIds = Stream.of(childIds).filter(id -> !detailsList.contains(id)).collect(Collectors.toList());
        } else {
            addIds = Arrays.asList(childIds);
        }

        addIds.stream().forEach(id -> {
            Optional<JobDetails> optionalDetails = this.repository.findById(id);
            if (optionalDetails.isPresent()) {
                this.repository.addJobRelation(jobId, id);
            } else {
                throw new RelayJobRuntimeException(MessageFormat.format("未找到任务编号[{0}]的任务信息", id));
            }
        });

    }

    public void deleteChildren(String jobId, String childrenId) {
        Assert.hasLength(childrenId, "子任务Id不能为空");
        Assert.hasLength(jobId, "任务编号不能为空");
        String[] childIds = childrenId.split(",");
        if (childIds == null) {
            throw new RelayJobRuntimeException("删除的子任务为空");
        }

        Stream.of(childIds).forEach(id -> this.repository.deleteChildrenJob(jobId, id));
    }

    public void deleteJob(String jobId) {
        Assert.hasLength(jobId, "任务编号不能为空");
        Optional<JobDetails> optional = this.repository.findById(jobId);
        if (!optional.isPresent()) {
            throw new RuntimeException(MessageFormat.format("未找到编号[{0}]的任务信息", jobId));
        }
        JobDetails jobDetails = optional.get();
        jobDetails.setDelFlag(BaseEntity.DelFlagEnum.DELETED.getFlag());
        this.scheduleConfigService.unloadJob(this.convert(jobDetails));
    }

    @Transactional(propagation = Propagation.SUPPORTS)
    public List<JobDetails> getChildrenJob(String jobId) {
        return this.repository.findChildrenJob(jobId);
    }

    public void updateJob(JobDetails details) {
        Assert.notNull(details, "Job detail not be null");
        Assert.hasLength(details.getId(), "Job id not be null");
        // 先更新数据库配置
        Optional<JobDetails> optional = this.repository.findById(details.getId());
        if (!optional.isPresent()) {
            throw new RelayJobRuntimeException(MessageFormat.format("Not found old job information, job id[{0}]", details.getId()));
        }
        // 应用 ID 和 处理器名称不能修改
        JobDetails jobDetails = optional.get();
        jobDetails.setTriggerClassName(details.getTriggerClassName());
        jobDetails.setBlockStrategy(details.getBlockStrategy());
        jobDetails.setExecuteModel(details.getExecuteModel());
        jobDetails.setRouteStrategy(details.getRouteStrategy());
        jobDetails.setCronExpression(details.getCronExpression());
        jobDetails.setDescription(details.getDescription());
        jobDetails.setExecuteTimeout(details.getExecuteTimeout());
        jobDetails.setFailRetryTimes(details.getFailRetryTimes());
        jobDetails.setJobName(details.getJobName());
        jobDetails.setTriggerParam(details.getTriggerParam());
        this.repository.save(jobDetails);
        // 更新调度服务
        this.scheduleConfigService.updateJob(this.convert(jobDetails));
    }

    @Transactional(propagation = Propagation.SUPPORTS)
    public void triggerNow(String jobId, String param) {
        Optional<JobDetails> optional = this.repository.findById(jobId);
        if (!optional.isPresent()) {
            throw new RelayJobRuntimeException("Job not found.");
        }

        Map<String, Object> paramMap = new HashMap<>(2);
        paramMap.put(JobDataConstant.CUSTOM_TRIGGER_PARAM_KEY, param);

        this.scheduleConfigService.runNow(this.convert(optional.get()), paramMap);
    }

}
